#include "hwifi.h"


static bool hwifi_wait_bit(EventBits_t bit, TickType_t timeout);
static void hwifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data);
static void hwifi_status_machine(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_update_unready(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_update_idle(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_update_connecting(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_update_connected(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_update_gotip(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_update_disconnecting(hwifi_driver_type driver);
static hwifi_status_type hwifi_status_set(hwifi_status_type status);
static void hwifi_direct_connect(const hwifi_connect_param_type *param);

static hwifi_status_type hwifi_status = hwifi_status_unready;   //状态机状态
static hwifi_target_type hwifi_target = hwifi_target_idle;      //目标状态
static hwifi_connect_param_type hwifi_connect_param;            //连接参数

static EventGroupHandle_t   hwifi_event_group;                  //用于等待连接或断开的事件组
#define IDLEEVENT_BIT       BIT0         //断开状态事件bit  
#define CONNECTEDEVENT_BIT  BIT1         //connected事件bit  
#define GOTIPEVENT_BIT      BIT2         //gotip事件bit  


void hwifi_init(void) {
    hwifi_event_group = xEventGroupCreate();

    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    esp_event_handler_instance_t instance_lost_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &hwifi_event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &hwifi_event_handler,
                                                        NULL,
                                                        &instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_LOST_IP,
                                                        &hwifi_event_handler,
                                                        NULL,
                                                        &instance_lost_ip));
       
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start() );

    HLOGI("wifi", "hwifi_init finished");
}

hwifi_status_type hwifi_get_status(void) {
    return hwifi_status;
}

bool hwifi_wait_idle(TickType_t timeout) {
    return hwifi_wait_bit(IDLEEVENT_BIT, timeout);
}

bool hwifi_wait_connected(TickType_t timeout) {
    return hwifi_wait_bit(CONNECTEDEVENT_BIT, timeout);
}

bool hwifi_wait_gotip(TickType_t timeout) {
    return hwifi_wait_bit(GOTIPEVENT_BIT, timeout);
}

void hwifi_connect(const hwifi_connect_param_type *param) {
    strcpy((char *)hwifi_connect_param.ssid, (char *)param->ssid);
    strcpy((char *)hwifi_connect_param.password, (char *)param->password);
    hwifi_target = hwifi_target_reconnect;
    HLOGI("wifi_event", "%d,hwifi_connect:%s %s", hwifi_status, (char *)hwifi_connect_param.ssid, (char *)hwifi_connect_param.password);
    hwifi_status_machine(hwifi_driver_set);
}

void hwifi_reconnect(void) {
    hwifi_target = hwifi_target_reconnect;
    HLOGI("wifi_event", "%d,hwifi_reconnect", hwifi_status);
    hwifi_status_machine(hwifi_driver_set);
}

void hwifi_disconnect(void) {
    hwifi_target = hwifi_target_idle;
    HLOGI("wifi_event", "%d,hwifi_disconnect", hwifi_status);
    hwifi_status_machine(hwifi_driver_set);
}

static bool hwifi_wait_bit(EventBits_t bit, TickType_t timeout) {
    EventBits_t value = xEventGroupWaitBits(hwifi_event_group,
            bit,
            pdFALSE,
            pdFALSE,
            timeout);
    
    if((value & bit) == bit){
        return true;
    }
    else {
        return false;
    }
}

static void hwifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    hwifi_driver_type driver;
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        HLOGI("wifi_event", "%d,hwifi_driver_start", hwifi_status);
        driver = hwifi_driver_start;
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
        HLOGI("wifi_event", "%d,hwifi_driver_connect", hwifi_status);
        driver = hwifi_driver_connect;
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        HLOGI("wifi_event", "%d,hwifi_driver_disconnect", hwifi_status);
        driver = hwifi_driver_disconnect;
    } 
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        HLOGI("wifi_event", "%d,hwifi_driver_gotip", hwifi_status);
        driver = hwifi_driver_gotip;
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) {
        HLOGI("wifi_event", "%d,hwifi_driver_lostip", hwifi_status);
        driver = hwifi_driver_lostip;
    }
    else {
        HLOGI("wifi_event", "event_base:%s,event_id:%d,hwifi_status:%d,hwifi_driver_unkonw", event_base, event_id, hwifi_status);
        driver = hwifi_driver_unkonw;
    }
    if(hwifi_driver_unkonw != driver) {
        hwifi_status_machine(driver);
        HLOGI("hwifi", "wifi status: %d", hwifi_status);
    }
}

static void hwifi_status_machine(hwifi_driver_type driver) {
    switch(hwifi_status) {
        case hwifi_status_unready : hwifi_status = hwifi_status_update_unready(driver); break;
        case hwifi_status_idle : hwifi_status = hwifi_status_update_idle(driver); break;
        case hwifi_status_connecting : hwifi_status = hwifi_status_update_connecting(driver); break;
        case hwifi_status_connected : hwifi_status = hwifi_status_update_connected(driver); break;
        case hwifi_status_gotip : hwifi_status = hwifi_status_update_gotip(driver); break;
        case hwifi_status_disconnecting : hwifi_status = hwifi_status_update_disconnecting(driver); break;
        default : break;
    }
    switch(hwifi_status) {
        case hwifi_status_idle : {
            xEventGroupClearBits(hwifi_event_group, CONNECTEDEVENT_BIT | GOTIPEVENT_BIT);
            xEventGroupSetBits(hwifi_event_group, IDLEEVENT_BIT);
            break;
        }
        case hwifi_status_connected : {
            xEventGroupClearBits(hwifi_event_group, IDLEEVENT_BIT | GOTIPEVENT_BIT);
            xEventGroupSetBits(hwifi_event_group, CONNECTEDEVENT_BIT);
            break;
        }
        case hwifi_status_gotip : {
            xEventGroupClearBits(hwifi_event_group, IDLEEVENT_BIT | CONNECTEDEVENT_BIT);
            xEventGroupSetBits(hwifi_event_group, GOTIPEVENT_BIT);
            break;
        }
        default : {
            xEventGroupClearBits(hwifi_event_group, IDLEEVENT_BIT | CONNECTEDEVENT_BIT | GOTIPEVENT_BIT);
            break;
        }
    }
}

static hwifi_status_type hwifi_status_update_unready(hwifi_driver_type driver) {
    hwifi_status_type temp_status;
    switch (driver) {
        case hwifi_driver_start:
            temp_status = hwifi_status_idle;
            break;
        case hwifi_driver_set:
            temp_status = hwifi_status_unready;
            break;
        default:
            HLOGE("wifi status", "invalid driver %d in unready status", driver);
            temp_status = hwifi_status_unknow;
            break;
    }
    return hwifi_status_set(temp_status);
}

static hwifi_status_type hwifi_status_update_idle(hwifi_driver_type driver) {
    hwifi_status_type temp_status;
    switch (driver) {
        case hwifi_driver_lostip :
        case hwifi_driver_set :
            temp_status = hwifi_status_idle;
            break;
        default:
            HLOGE("wifi status", "invalid driver %d in idle status", driver);
            temp_status = hwifi_status_unknow;
            break;
    }
    return hwifi_status_set(temp_status);
}

static hwifi_status_type hwifi_status_update_connecting(hwifi_driver_type driver) {
    hwifi_status_type temp_status;
    switch (driver) {
        case hwifi_driver_connect:
            temp_status = hwifi_status_connected;
            break;
        case hwifi_driver_disconnect:
            temp_status = hwifi_status_idle;
            break;
        case hwifi_driver_lostip:
            temp_status = hwifi_status_connecting;
            break;
        case hwifi_driver_set:
            temp_status = hwifi_status_connecting;
            break;
        default:
            HLOGE("wifi status", "invalid driver %d in connecting status", driver);
            temp_status = hwifi_status_unknow;
            break;
    }
    return hwifi_status_set(temp_status);
}

static hwifi_status_type hwifi_status_update_connected(hwifi_driver_type driver) {
    hwifi_status_type temp_status;
    switch (driver) {
        case hwifi_driver_gotip:
            temp_status = hwifi_status_gotip;
            break;
        case hwifi_driver_set:
            temp_status = hwifi_status_connected;
            break;
        case hwifi_driver_disconnect:
            temp_status = hwifi_status_idle;
            break;
        default:
            HLOGE("wifi status", "invalid driver %d in connected status", driver);
            temp_status = hwifi_status_unknow;
            break;
    }
    return hwifi_status_set(temp_status);
}

static hwifi_status_type hwifi_status_update_gotip(hwifi_driver_type driver) {
    hwifi_status_type temp_status;
    switch (driver) {
        case hwifi_driver_lostip:
            temp_status = hwifi_status_connected;
            break;
        case hwifi_driver_disconnect:
            temp_status = hwifi_status_idle;
            break;
        case hwifi_driver_set:
            temp_status = hwifi_status_gotip;
            break;
        default:
            HLOGE("wifi status", "invalid driver %d in gotip status", driver);
            temp_status = hwifi_status_unknow;
            break;
    }
    return hwifi_status_set(temp_status);
}

static hwifi_status_type hwifi_status_update_disconnecting(hwifi_driver_type driver) {
    hwifi_status_type temp_status;
    switch (driver) {
        case hwifi_driver_disconnect:
            temp_status = hwifi_status_idle;
            break;
        case hwifi_driver_set:
            temp_status = hwifi_status_disconnecting;
            break;
        default:
            HLOGE("wifi status", "invalid driver %d in disconnecting status", driver);
            temp_status = hwifi_status_unknow;
            break;
    }
    return hwifi_status_set(temp_status);
}

//根据状态处理设置请求，并返回新状态
static hwifi_status_type hwifi_status_set(hwifi_status_type status) {
    switch (status) {
        case hwifi_status_unknow : {
            HLOGE("wifi status", "unknow status");
            return hwifi_status_unknow;
        }
        case hwifi_status_unready : {
            return hwifi_status_unready;
        }
        case hwifi_status_idle : {
            switch(hwifi_target) {
                case hwifi_target_idle : {
                    return hwifi_status_idle;
                }
                case hwifi_target_reconnect : {
                    hwifi_target = hwifi_target_gotip;
                }
                case hwifi_target_gotip : {
                    hwifi_direct_connect(&hwifi_connect_param);
                    return hwifi_status_connecting;
                }
            }
        }
        case hwifi_status_connecting : {
            return hwifi_status_connecting;
        }
        case hwifi_status_connected : {
            switch(hwifi_target) {
                case hwifi_target_gotip : {
                    return hwifi_status_connected;
                }
                case hwifi_target_reconnect :
                case hwifi_target_idle : {
                    esp_wifi_disconnect();
                    return hwifi_status_disconnecting;
                }
            }
        }
        case hwifi_status_gotip : {
            switch(hwifi_target) {
                case hwifi_target_gotip : {
                    return hwifi_status_gotip;
                }
                case hwifi_target_reconnect :
                case hwifi_target_idle : {
                    esp_wifi_disconnect();
                    return hwifi_status_disconnecting;
                }
            }
        }
        case hwifi_status_disconnecting : {
            return hwifi_status_disconnecting;
        }
        default: return hwifi_status_unknow;
    }
}


static void hwifi_direct_connect(const hwifi_connect_param_type *param) {
    wifi_config_t wifi_config = {
        .sta = {
	        .threshold.authmode = WIFI_AUTH_WPA2_PSK,
            .pmf_cfg = {
                .capable = true,
                .required = false
            },
        },
    };
    memcpy(wifi_config.sta.ssid, param->ssid, 32);
    memcpy(wifi_config.sta.password, param->password, 64);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    esp_wifi_connect();
}

